package com.bsj.media.media;

import com.bsj.power.common.util.Conv;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class FlvPack {


    /**
     * 创建flv头
     *
     * @param Flags：流的信息--有视频（0x01）,有音频（0x4）,有视频又有音频就是0x01 | 0x04（0x05）
     * @return 当前数据大小，失败返回0
     */
    static public int createFlvHeader(byte[] data, int dataOffset, int Flags) {

        // 前3个bytes是文件类型，总是“FLV”，也就是（0x46 0x4C 0x56）。
        data[dataOffset++] = 0x46;
        data[dataOffset++] = 0x4C;
        data[dataOffset++] = 0x56;

        // 第4btye是版本号，目前一般是0x01。
        data[dataOffset++] = 0x01;

        // 第5byte是流的信息，倒数第一bit是1表示有视频（0x01），倒数第三bit是1表示有音频（0x4），有视频又有音频就是0x01 |
        // 0x04（0x05），其他都应该是0。
        data[dataOffset++] = (byte) Flags;

        // 最后4bytes表示FLV 头的长度。
        data[dataOffset++] = 0x00;
        data[dataOffset++] = 0x00;
        data[dataOffset++] = 0x00;
        data[dataOffset++] = 0x09;

        return dataOffset;
    }

    /**
     * 获取flv pre tag size (4字节) + tag header(11Byte)
     *
     * @param data       数据体
     * @param dataOffset pre tag size (4字节) + tag header(11Byte)写在data的开始位置
     * @param type       tag的类型 音频（0x8），视频（0x9），脚本（0x12）
     * @param timeStamp  时间戳
     * @return 当前数据大小，失败返回0
     */
    static public int setBodyHeader(byte[] data, int dataOffset, int type, int timeStamp, int preTagSize,
                                    int curTagSize) {

        int offset = dataOffset;
        int len = data.length;

        // 长度校验--pre tag size (4字节) + tag header(11Byte)
        if (len - offset < 15) {
            log.warn("缓存区长度不够：len=" + len + " offset=" + offset);
            return 0;
        }

        if (0x8 != type && 0x9 != type && 0x12 != type) {
            log.warn("tag的类型不正确：" + type);
            return 0;
        }

        // pre tag size(4Byte),默认为0
        Conv.setIntNetOrder(data, offset, preTagSize);
        offset += 4;

        // tag header(11Byte)

        // 第1个byte为记录着tag的类型，音频（0x8），视频（0x9），脚本（0x12）
        data[offset++] = (byte) type;

        // 第2到4bytes是数据区的长度，也就是tag data的长度,3字节
        Conv.setIntNetOrder_3(data, offset, curTagSize);
        offset += 3;

        // 3个bytes是时间戳，单位是毫秒，类型为0x12则时间戳为0
        Conv.setIntNetOrder_3(data, offset, timeStamp);
        offset += 3;

        // 时间戳后面一个byte是扩展时间戳，时间戳不够长的时候用
        data[offset++] = (byte) (timeStamp >> 24 & 0xFF);
        // data[offset++] = 0x0;

        // 最后3bytes是streamID，但是总为0。
        data[offset++] = 0x0;
        data[offset++] = 0x0;
        data[offset++] = 0x0;

        return offset;
    }

    /**
     * 获取flv脚本
     *
     * @param data            数据体
     * @param dataOffset      tag data写在data的开始位置
     * @param videoFrameRate  视频帧率
     * @param width           视频宽
     * @param height          视频高
     * @param videoCodecid    视频编码类型  7：H264 12:H265
     * @param audioSampleRate 音频采样率
     * @param samplesize      音频采样大小
     * @param audioCodecid    音频编码类型 10 AAC
     * @param duration        文件持续时间
     * @param filesize        文件大小
     * @return 当前数据大小
     */
    static public int setScriptBody(byte[] data, int dataOffset, double videoFrameRate, double width, double height,
                                    int videoCodecid, int audioSampleRate, int samplesize, int audioCodecid, int duration, int filesize) {

        int offset = dataOffset;
        int len = data.length;

        // 长度校验--tag data 默认最小长度不能小于512字节，实际Script需使用到的空间
        // 比这个小，但是这个缓存是预分配的，需要分配足够大的空间
        if (len - offset < 512) {
            log.warn("缓存区长度不够：len=" + len + " offset=" + offset);
            return 0;
        }

        // 第一个AMF包
        // 第1个字节表示AMF包类型，一般总是0x02，表示字符串，其他值表示意义请查阅文档
        data[offset++] = 0x02;

        // 第2-3个字节为UI16类型值，表示字符串的长度，一般总是0x000A（“onMetaData”长度）。
        // 后面字节为字符串数据，一般总为“onMetaData”。
        offset = x264_put_amf_string(data, offset, "onMetaData");

        // 第二个AMF包：
        // 第1个字节表示AMF包类型，一般总是0x08，表示数组。
        data[offset++] = 0x08;

        // 第2-5个字节为UI32类型值，表示数组元素的个数,7表示后面带有7个参数
        int paramSizeOffset = offset;
        int paramSize = 0;
        // Conv.setIntNetOrder(data, offset, 7);
        offset += 4;

        // 参数
        if (width != 0 && height != 0 && videoFrameRate != 0) {
            // 视频宽度
            offset = x264_put_amf_string(data, offset, "width");
            offset = x264_put_amf_double(data, offset, width);

            // 视频高度
            offset = x264_put_amf_string(data, offset, "height");
            offset = x264_put_amf_double(data, offset, height);

            // 视频帧率
            offset = x264_put_amf_string(data, offset, "framerate");
            offset = x264_put_amf_double(data, offset, videoFrameRate);

            // 视频码率 暂时无法统计，不设置
            // offset = x264_put_amf_string(data, offset, "videodatarate");
            // offset = x264_put_amf_double(data, offset, 184.00);

            // 视频编码方式
            offset = x264_put_amf_string(data, offset, "videocodecid");
            offset = x264_put_amf_double(data, offset, videoCodecid);

            paramSize += 4;
        }

        if (audioSampleRate != 0 && audioCodecid != 0 && samplesize != 0) {
            // 音频采样率
            offset = x264_put_amf_string(data, offset, "audiosamplerate");
            offset = x264_put_amf_double(data, offset, audioSampleRate);

            // 音频精度
            offset = x264_put_amf_string(data, offset, "audiosamplesize");
            offset = x264_put_amf_double(data, offset, samplesize);

            // 音频是否为立体声:1立体声 0:不是立体声
            offset = x264_put_amf_string(data, offset, "stereo");
            offset = x264_put_amf_bool(data, offset, (byte) 0);

            // 音频编码方式
            offset = x264_put_amf_string(data, offset, "audiocodecid");
            offset = x264_put_amf_double(data, offset, audioCodecid);

            paramSize += 4;
        }

        if (duration > 0) {
            paramSize += 2;

            // 时长，未知置为0，后期可以修改
            offset = x264_put_amf_string(data, offset, "duration");
            offset = x264_put_amf_double(data, offset, duration);

            // 文件大小，未知置为0，后期可以修改
            offset = x264_put_amf_string(data, offset, "filesize");
            offset = x264_put_amf_double(data, offset, filesize);
        }

        // 设置参数个数
        Conv.setIntNetOrder(data, paramSizeOffset, paramSize);

        // 结束符
        offset = x264_put_amf_string(data, offset, "");
        data[offset++] = 0x09;

        return offset;
    }

    /**
     * 获取flv脚本
     *
     * @param data            数据体
     * @param dataOffset      tag data写在data的开始位置
     * @param videoFrameRate  视频帧率
     * @param width           视频宽
     * @param height          视频高
     * @param videoCodecid    视频编码类型
     * @param audioSampleRate 音频采样率
     * @param samplesize      音频采样大小
     * @param audioCodecid    音频编码类型 10 AAC
     * @param duration        文件持续时间
     * @param filesize        文件大小
     * @return 当前数据大小
     */
    static public int setScriptBodyForDownload(byte[] data, int dataOffset, double videoFrameRate, double width, double height,
                                               int videoCodecid, int audioSampleRate, int samplesize, int audioCodecid, int duration, int filesize) {

        int offset = dataOffset;
        int len = data.length;

        // 长度校验--tag data 默认最小长度不能小于512字节，实际Script需使用到的空间
        // 比这个小，但是这个缓存是预分配的，需要分配足够大的空间
        if (len - offset < 512) {
            log.warn("缓存区长度不够：len=" + len + " offset=" + offset);
            return 0;
        }

        // 第一个AMF包
        // 第1个字节表示AMF包类型，一般总是0x02，表示字符串，其他值表示意义请查阅文档
        data[offset++] = 0x02;

        // 第2-3个字节为UI16类型值，表示字符串的长度，一般总是0x000A（“onMetaData”长度）。
        // 后面字节为字符串数据，一般总为“onMetaData”。
        offset = x264_put_amf_string(data, offset, "onMetaData");

        // 第二个AMF包：
        // 第1个字节表示AMF包类型，一般总是0x08，表示数组。
        data[offset++] = 0x08;

        // 第2-5个字节为UI32类型值，表示数组元素的个数,7表示后面带有7个参数
        int paramSizeOffset = offset;
        int paramSize = 0;
        // Conv.setIntNetOrder(data, offset, 7);
        offset += 4;

        // 参数
        if (width != 0 && height != 0 && videoFrameRate != 0) {
            // 视频宽度
            offset = x264_put_amf_string(data, offset, "width");
            offset = x264_put_amf_double(data, offset, width);

            // 视频高度
            offset = x264_put_amf_string(data, offset, "height");
            offset = x264_put_amf_double(data, offset, height);

            // 视频帧率
            offset = x264_put_amf_string(data, offset, "framerate");
            offset = x264_put_amf_double(data, offset, videoFrameRate);


            // 视频编码方式
            offset = x264_put_amf_string(data, offset, "videocodecid");
            offset = x264_put_amf_double(data, offset, videoCodecid);

            paramSize += 4;
        }

        if (audioSampleRate != 0 && audioCodecid != 0 && samplesize != 0) {
            // 音频采样率
            offset = x264_put_amf_string(data, offset, "audiosamplerate");
            offset = x264_put_amf_double(data, offset, audioSampleRate);

            // 音频精度
            offset = x264_put_amf_string(data, offset, "audiosamplesize");
            offset = x264_put_amf_double(data, offset, samplesize);

            // 音频是否为立体声:1立体声 0:不是立体声
            offset = x264_put_amf_string(data, offset, "stereo");
            offset = x264_put_amf_bool(data, offset, (byte) 0);

            // 音频编码方式
            offset = x264_put_amf_string(data, offset, "audiocodecid");
            offset = x264_put_amf_double(data, offset, audioCodecid);

            paramSize += 4;
        }

        if (duration > 0) {
            paramSize += 2;

            // 时长，未知置为0，后期可以修改
            offset = x264_put_amf_string(data, offset, "duration");
            offset = x264_put_amf_double(data, offset, duration);

            // 文件大小，未知置为0，后期可以修改
            offset = x264_put_amf_string(data, offset, "filesize");
            offset = x264_put_amf_double(data, offset, filesize);
        }

        // 设置参数个数
        Conv.setIntNetOrder(data, paramSizeOffset, paramSize);


        return offset;
    }

    /**
     * 获取flv 音频数据
     *
     * @param data
     *            数据体
     * @param dataOffset
     *            tag data写在data的开始位置
     * @param dataFd
     *            fd数据
     * @param dataFdOffset
     *            dataFd
     * @param dataLen
     *            AAC数据长度
     * @param audioCodecid
     *            音频编码类型
     * @param audioSampleRate
     *            音频采样率
     * @param audioSampleSize
     *            音频采样大小
     * @param audioCodecid
     *            音频编码类型
     * @param channel
     *            音频声道
     * @param isFirstAuido
     *            第一个audio Tag帧
     * @return 当前数据大小
     */
    /**
     * 获取flv 音频数据
     *
     * @param dstbuffer     目的地址
     * @param dstOffset     起始偏移位
     * @param srcData       源数据地址
     * @param srcDataOffset 源数据起始偏移位
     * @param dataLen       源数据长度
     * @param codecid       编码id
     * @param samplerate    采样率
     * @param samplesize    采样位数
     * @param channel       音频声道数
     * @param isFirstAudio  判断是否是第一个音频帧
     * @return 返回设置数据后，偏移位
     */
    static public int setAudioBody(byte[] dstbuffer, int dstOffset, byte[] srcData, int srcDataOffset, int dataLen,
                                   int codecid, int samplerate, int samplesize, int channel, boolean isFirstAudio) {

        int offset = dstOffset;
        int len = dstbuffer.length;

        int buffLen = dataLen;

        // 长度校验--tag data 音频数据类型：1 + 音频属性：1 + dataLen（n）= 2+buffLen
        if ((2 + buffLen) > (len - offset)) {
            log.warn("缓存区长度不够：len=" + len + " offset=" + offset);
            return 0;
        }
        // 第一字节是标识SoundFomat SoundSize SoundRate SoundFormat
        dstbuffer[offset++] = (byte) ((codecid << 4) | (samplerate << 2) | (samplesize << 1) | channel);

        if (isFirstAudio) {
            dstbuffer[offset++] = 0x00;
            // AAC AudioSpecificConfig 5字节
            // Object Type(5bits) 2 -AAC-LC 5 -SBR 29 -PS
            int temp = 0x2 << 3;
            dstbuffer[offset] = 0x00;
            dstbuffer[offset] |= temp;
            // SampleRate Index(4bits):0 -96000, 1-88200, 3-64000, 4-44100 5-32000
            // 6- 24000, 7 -22050 8 -16000, 11-8000
            if (samplerate == 3) {// 44k
                temp = 0x4 >> 1;
            } else if (samplerate == 1) {// 11K--> 16000
                temp = 0x8 >> 1;
            }
            dstbuffer[offset++] |= temp;
            // Channels(4bits)
            if (channel == 1)
                // 立体声
                temp = 0x2 << 3;
            else if (channel == 0) {
                // 单声道
                temp = 0x1 << 3;
            }
            dstbuffer[offset] = 0x00;
            dstbuffer[offset++] |= temp;
            // Frame Length Flag(1bit) 0 -1024
            // Depend On Core Coder(1bit)
            // Extension Flag(1bit)
            // Sync Extension Type(11bits)
            // Enternsion Audio Object Type(5bits)
            // SBR Present FLag(1bit)
            dstbuffer[offset++] = 0x00;
            dstbuffer[offset++] = 0x00;
            dstbuffer[offset++] = 0x00;
        } else {
            dstbuffer[offset++] = 0x01;
            // 接下来是AAC raw
            System.arraycopy(srcData, srcDataOffset, dstbuffer, offset, buffLen);
            offset += buffLen;
        }

        return offset;
    }

    /**
     * 设置flv第一个video的头
     *
     * @param data       目的缓存区
     * @param dataOffset 目的写入起始地址
     * @param sps        系列参数集
     * @param pps        图片参数集
     * @param codecId    编码id 0x7:H264(AVC)  0x12:H265(HEVC)
     * @return 当前数据大小
     */
    static public int setFirstVideoHead(byte[] data, int dataOffset, byte[] sps, byte[] pps, byte codecId) {

        int offset = dataOffset;
        int len = data.length;

        int buffLen = sps.length + pps.length;

        // 长度校验--tag data 视频数据类型：1 + 视频属性：1 + dataLen（n）= 2+buffLen
        if ((2 + buffLen) > (len - offset)) {
            log.warn("缓存区长度不够：len=" + len + " offset=" + offset);
            return 0;
        }

        // Frametype and CodecID
        data[offset++] = (byte) (0x10 | codecId);

        // AVC sequence header
        data[offset++] = 0;

        // composition time
        Conv.setIntNetOrder_3(data, offset, 0);
        offset += 3;

        // version
        data[offset++] = 1;

        data[offset++] = sps[1 + 4];// profile
        data[offset++] = sps[2 + 4];// profile
        data[offset++] = sps[3 + 4];// profile

        data[offset++] = (byte) 0xff;// 6 bits reserved (111111) + 2 bits nal size length - 1 (11)
        data[offset++] = (byte) 0xe1;// 3 bits reserved (111) + 5 bits number of sps (00001)

        // sps长度+数据
        Conv.setShortNetOrder(data, offset, sps.length - 4);
        offset += 2;
        System.arraycopy(sps, 4, data, offset, sps.length - 4);
        offset += (sps.length - 4);

        // pps 数量
        data[offset++] = 1;

        // pps长度+数据
        Conv.setShortNetOrder(data, offset, pps.length - 4);
        offset += 2;
        System.arraycopy(pps, 4, data, offset, pps.length - 4);
        offset += (pps.length - 4);

        return offset;
    }

    /**
     * @param data:
     * @param dataOffset:
     * @param vps:
     * @param sps:
     * @param pps:
     * @param codecId:
     * @Description: 设置flv(h265)第一个video的头
     * @return: int
     **/
    static public int setFirstVideoHead(byte[] data, int dataOffset, byte[] vps, byte[] sps, byte[] pps, byte codecId) {
        int offset = dataOffset;
        // Frametype and CodecID
        data[offset++] = 0x1C;
        data[offset++] = 0x00;
        data[offset++] = 0x00;
        data[offset++] = 0x00;
        data[offset++] = 0x00;
        // configurationVersion = 01
        data[offset++] = 0x01;
        offset += 20; // 先不对20字节进行赋值,默认是0x00
        //------------------------------
        data[offset++] = 0x03; // lengthSizeMinusOne
        data[offset++] = 0x03; // numOfArrays
        // vps
        data[offset++] = 0x20; // vps 32
        data[offset++] = (1 >> 8) & 0xff;
        data[offset++] = 1 & 0xff;
        int vpsHeadSize = (vps[2] == 0x01) ? 3 : 4;
        Conv.setShortNetOrder(data, offset, vps.length - vpsHeadSize);
        offset += 2;
        System.arraycopy(vps, vpsHeadSize, data, offset, vps.length - vpsHeadSize);
        offset += (vps.length - vpsHeadSize);
        // sps
        int spsHeadSize = (sps[2] == 0x01) ? 3 : 4; // sps帧头大小
        data[offset++] = 0x21; // sps 33
        data[offset++] = (1 >> 8) & 0xff;
        data[offset++] = 1 & 0xff;
        Conv.setShortNetOrder(data, offset, sps.length - spsHeadSize);
        offset += 2;
        System.arraycopy(sps, spsHeadSize, data, offset, sps.length - spsHeadSize);
        offset += (sps.length - spsHeadSize);
        // pps
        data[offset++] = 0x22; // pps 34
        data[offset++] = (1 >> 8) & 0xff;
        data[offset++] = 1 & 0xff;
        int ppsHeadSize = (pps[2] == 0x01) ? 3 : 4;
        Conv.setShortNetOrder(data, offset, pps.length - ppsHeadSize);
        offset += 2;
        System.arraycopy(pps, ppsHeadSize, data, offset, pps.length - ppsHeadSize);
        offset += (pps.length - ppsHeadSize);

        return offset;
    }

    /**
     * 获取flv 视频数据
     *
     * @param data          数据
     * @param dataOffset    tag data写在data的开始位置
     * @param dataFd
     * @param dataFdOffset, 去掉了h264 start code 00 00 00 01
     * @param naluLen       当前h264 帧长度 去掉帧头的长度
     * @param codecid       编码器类型 7 AVC/h264
     * @param keyFrame      关键帧用1，非关键帧用2
     * @param frameRate     帧率，用于计算相对时间戳
     * @return 返回新的偏移位
     */
    static public int setVideoBody(byte[] data, int dataOffset, byte[] dataFd, int dataFdOffset, int naluLen,
                                   int codecid, int keyFrame, int frameRate) {

        int offset = dataOffset;
        int len = data.length;

        // 长度校验--tag data 视频数据类型：1 + 视频属性：1 + dataLen（n）= 2+buffLen
        if ((2 + naluLen) > (len - offset)) {
            log.warn("缓存区长度不够：len=" + len + " offset=" + offset + " naluLen=" + naluLen);
            return 0;
        }

        // 高4比特定义帧格式：1 关键帧； 2 内部帧（非IDR帧）；3可丢弃内部帧（仅对H.263有用）
        // 低4比特定义编码器：2：H.263, 4: VP6, 7:H264; ...
        // 对于264的视频FLV，这个字节一般为 0x17或者0x27.
        data[offset++] = (byte) (keyFrame << 4 | codecid);

        // AVCPacketType 0x00表示 AVC sequnence 0x01 AVC nalu帧 0x02 avc end of sequnence
        data[offset++] = (byte) 0x01;

        // composition time 相对时间戳
        Conv.setIntNetOrder_3(data, offset, frameRate);
        offset += 3;

        // 数据长度 nalu length nalu帧长度
        Conv.setIntNetOrder(data, offset, naluLen);
        offset += 4;

        // 数据区
        System.arraycopy(dataFd, dataFdOffset, data, offset, naluLen);
        offset += naluLen;

        return offset;
    }

    /**
     * 写入String 的长度+数据体
     *
     * @param data       数据体
     * @param dataOffset data的开始位置
     * @param str        需要写入的数据内容
     * @return 当前数据大小
     */
    static private int x264_put_amf_string(byte[] data, int dataOffset, String str) {
        int offset = dataOffset;

        // 第1-2个字节为UI16类型值，表示字符串的长度。
        Conv.setShortNetOrder(data, offset, str.length());
        offset += 2;

        // 后面字节为字符串数据。
        System.arraycopy(str.getBytes(), 0, data, offset, str.getBytes().length);
        offset += str.getBytes().length;

        return offset;
    }

    /**
     * 写入double 的类型+数据体
     *
     * @param data       数据体
     * @param dataOffset data的开始位置
     * @param d          需要写入的数据内容
     * @return 返回新的偏移位
     */
    static private int x264_put_amf_double(byte[] data, int dataOffset, double d) {
        int offset = dataOffset;

        // 元素值的类型 double为0--1字节
        data[offset++] = 0;

        Conv.setDoubleNetOrder(data, offset, d);
        offset += 8;

        return offset;
    }

    /**
     * amf 写入BOOL值类型 AMF_TYPE_DATA_BOOL
     *
     * @param data       数据体
     * @param dataOffset 起始偏移位
     * @param b          需要写入的数据
     * @return 返回新的偏移位
     */
    static private int x264_put_amf_bool(byte[] data, int dataOffset, byte b) {
        int offset = dataOffset;

        // 元素值的类型BOOL为1 -- 1字节
        data[offset++] = 1;
        //
        data[offset++] = b;

        return offset;
    }
}
